Workflow Engine

On this page:

Variable Workflow Tasks

Variable Workflow Tasks

deepmerge

The deepmerge task folds two or more objects together to create a single object with the precedent that the last merged object is used if a conflict occurs. It allows for combining objects, for example parts of a service instance, into an object representing a whole service instance. The task can create new data structures to match an existing task's parameters, which allows us to reuse existing tasks without new programming. A deepmerge task can combine data from job variables, static data, and tasks' outgoing variables, but this task only operates on objects; it does not operate on numbers, booleans, strings, or arrays.

When merge conflicts occur, the conflicting key's value in the last merged object is used. See the examples below to illustrate various merge outcomes.

The deepmerge task merges elements at any depth.

Properties

Property Description
Key The key field is not used and will be ignored.
Task Required. Specify where to find a variable to merge. You can merge data stored in job variables, a static value, or an earlier task's outgoing variable.
Variable Required. Specify the data to merge in the Variable field. When the data comes from the job or an earlier task, select the variable from the dropdown list. If the data comes from a static value, type the value in the variable edit box.
Add/Remove/Reorder Add, remove, and reorder the merged data.

Control Buttons

Control Icon Function
Add row + Add a row below the relevant row.
Delete row - Remove a row.
Move row up Up Arrow Button Move row up one row.
Move row down Down Arrow Button Move row down one row.

Example 1

Given the following objects.

Object 1

{
    "customer": "Wet Paint"
}

Object 2

{
    "service": "L3VPN"
}

Object 3

{
    "active": true
}

Result

The result will be.

{
    "customer": "Wet Paint",
    "service": "L3VPN",
    "active": true
}

Example 2

Given the following objects.

Object 1

{
    "fruit": {
        "apple": {
            "shape": {
                "round": {
                    "red": {
                        "flavor": "sweet"
                    }
                }
            }
        }
    }
}

Object 2

{
    "fruit": {
        "apple": {
            "shape": {
                "round": {
                    "red": {
                        "ripe": false
                    }
                }
            }
        }
    }
}

Object 3

{
    "fruit": {
        "apple": {
            "shape": {
                "round": {
                    "red": {
                        "quantity": 32
                    }
                }
            }
        }
    }
}

Result

The result will be.

{
    "fruit": {
        "apple": {
            "shape": {
                "round": {
                    "red": {
                        "flavor": "sweet",
                        "ripe": false,
                        "quantity": 32
                    }
                }
            }
        }
    }
}

Conflicting Key Example 1

Given the following objects.

Object 1

{ "fruit": "apple" }

Object 2

{ "fruit": 20 }

Result

The result will be.

{ "fruit": 20" }

Conflicting Key Example 2

Given the following objects.

Object 1

{
    "fruit": [
        {
            "name": "apple"
        }
    ]
}

Object 2

{
    "fruit": [
        {
            "name": "orange"
        }
    ]
}

Result

The result will be.

{
    "fruit": [
        {
            "name": "orange"
        }
    ]
}

Non-conflicting Key Example

The following example does not have a key conflict.

Object 1

{
    "quantity": [
        {
            "name": "apple"
        }
    ]
}

Object 2

{
    "quantity": [
        {
            "color": "red"
        }
    ]
}

Result

The result will be.

{
    "quantity": [
        {
            "name": "apple",
            "color": "red"
        }
    ]
}

merge

The merge task allows you to combine data from job variables, static data, and tasks' outgoing variables to create a new variable. By merging data, you can create new data structures to match an existing task's parameters, which allows you to reuse existing tasks without programming new tasks. A merge task can combine number, boolean, string, array, and object data types.

When merging data, one of two modes are supported.

  • no keys assigned to merged data - The merge task returns an array of the merged values in a non-deterministic order. See the examples below.
  • unique keys assigned to merged data - The merge task returns an object with key/value fields as specified. See the examples for this task.

Note: The merge task does not support merging data with partial key assignments or non-unique keys.

Properties

Property Description
Key Optional. Specify a key for each merged value. Supported configurations are either no keys or all unique keys.
Task Required. Specify where to find a variable to merge. You can merge data stored in job variables, a static value, or an earlier task's outgoing variable.
Variable Specify the data to merge in the Variable field. When the data comes from the job or an earlier task, select the variable from the dropdown list. If the data comes from a static value, type the value in the variable edit box.
Add/Remove/Reorder Add, remove, and reorder the merged data.

Control Buttons

Control Icon Function
Add row + Add a row below the relevant row.
Delete row - Remove a row.
Move row up Up Arrow Button Move row up one row.
Move row down Down Arrow Button Move row down one row.

Example

  1. Create a three-element array by merging data returned from three different tasks.

  2. Assume three tasks return these objects:

    {
        "vendor": "Cisco",
        "platforms": [ "3945", "6500" ],
        "chassis": [ "fixed", "modular" ]
    }
    
    {
        "release": "15.5"
    }
    
    {
        "features": [ "L3VPN", "STP" ]
    }
  3. Configure the merge task properties with no keys as shown below.

    Figure 1: Insert Merge

    Merge

  4. The merge task returns an array and the order is not deterministic.

    [
        {
            "vendor": "Cisco",
            "platforms": [
                "3945",
                "6500"
            ],
            "chassis": [
                "fixed",
                "modular"
            ]
        },
        {
            "release": "15.5"
        },
        {
            "features": [
                "L3VPN",
                "STP"
            ]
        }
    ]
  5. Create an object by assigning unique keys to each merged value.

    • In addition to the three objects used in the previous example, the merge includes a number, string, boolean, and array.
  6. Configure the merge task's properties with unique keys as shown below.

    Figure 2: Edit Merge

    Edit Merge Dialog

  7. The merge task returns an object.

    {
        "chassis": {
            "vendor": "Cisco",
            "platforms": [
                "3945",
                "6500"
            ],
            "chassis": [
                "fixed",
                "modular"
            ]
        },
        "os-image": {
            "release": "15.5"
        },
        "services": {
            "features": [
                "L3VPN",
                "STP"
            ]
        },
        "myNumber": 13,
        "myString": "hello world",
        "myBoolean": true,
        "myArray": [
            "apple",
            "pear"
        ]
    }

modify

The modify task creates a new variable from a modified copy of an existing variable. Its primary purpose is to modify an object key's boolean, number, or string value. Modify can create a new variable from both job and tasks' outgoing variables. It works with boolean, number, string, and object data types. It does not work against array data types, and it will not modify elements of an array in an object.

Properties

Input and output properties are shown below.

Incoming Required Description
object_to_update Yes Specify the variable to modify. You can modify job variables, a static value that can be useful when testing, or an earlier task's outgoing variable.
query Optional If not using a query, set its reference task to static and delete the contents of its reference variable. When modifying values that have data types of boolean, number, or string, the incoming variable query is not used. If modifying values of object type and you only need to modify a field, specify a query for that field. See example below.
new_value Yes The new value can come from a job variable, a static value, or an earlier task's outgoing variable. The new value will replace the value passed in the object_to_update parameter, but if object_to_update is an object and you specify a query, the new value will only replace the matching field. See example below.


Outgoing Description
updated_object The modified value is assigned to immutable variable updated_object and returned to the job. Optionally click the Job Variables check box to create a job variable from updated_object. The immutable variable updated_object remains, and a new mutable job variable is created. HINT: When creating a job variable, provide a new name to avoid name conflicts and provide clarity.

Example

  1. Change boolean value of a job variable or a previous task's outgoing variable having a value of false. The value of updated_object (outgoing variable) will be true.

    object_to_update = <name of var>
    query = ""
    new_value = true
  2. Change string value of a job variable or a previous task's outgoing variable having a value of "before". The value of updated_object (outgoing variable) will be after.

    object_to_update = <name of var>
    query = ""
    new_value = "after"
  3. Change boolean value of "sync-status" field in a specified object having a value of true.

    object_to_update = {
        "name": "cr1.atl",
        "sync-status": true
    }
    query = "sync-status"
    new_value = false

    The value of updated_object (outgoing variable) will look like this:

    {
        "name": "cr1.atl",
        "sync-status": false
    }
  4. Change string value of "description" field in specified object having value of "Northeast region."

    object_to_update = {
        "name": "BMW-1",
        "description": "Northeast region",
        "endpoints": [
            {
                "name": "NYC",
                "device": "er1.nyc"
            },
            {
                "name": "BOS",
                "device": "er1.bos"
            }
        ]
    }
    query = "description"
    new_value = "New England Region"
  5. The value of updated_object (outgoing variable) will look like this:

    {
        "name": "BMW-1",
        "description": "New England Region",
        "endpoints": [
            {
                "name": "NYC",
                "device": "er1.nyc"
            },
            {
                "name": "BOS",
                "device": "er1.bos"
            }
        ]
    }

newVariable

The newVariable task creates a new job variable. The result is similar to promoting an automatic task's outgoing variable to a job variable. You can create new boolean, number, string, object, or array variables.

Properties

Property Description
New Variable Name Type a name for the new variable.
Value Location Specify where to find the new variable's value (a job variable, a static value, or an earlier task). Select the appropriate reference task then either select the reference variable or type a new description for a static reference.
Value If value location is static, the Value field becomes a text edit box; type the new value. Otherwise the Value field becomes a dropdown list; select an existing job or task variable.

Example

Figure 3: New Job Variable Dialog

New Job Variable

query

The query task allows you to extract data out of object variables and to reshape that data for an existing task. For example, if data does not fit an existing task's recognized parameter, this task can reshape that data so it fits. Extracting data with a query and using an existing task is more efficient than writing a new task to fit data. You can also extract data from a variable for the purpose of performing an evaluation.

Properties

Input and output properties are shown below.

Incoming Type Required Description
pass_on_null Boolean Yes Controls what the query task returns when the query matches nothing. If true and the query matches nothing, the query task returns the queried data. If false, the most common setting, the query task returns null if it matches nothing. The value can come from a job variable, a static value, or an earlier task's outgoing variable. If the reference task is set to job, the reference variable shows a dropdown of available variable names for selection. If it is set to an earlier task, the reference variable will show the name of that task's outgoing variable. If the reference task is static, the reference variable becomes a pair of radio buttons for selecting true or false. When a query does not have a match and the pass_on_null is false, a null value will be returned but the query will transition to a failure.
query String Yes Specifies our query expression. It is similar to how you may perform lookups in JavaScript. Consult the json-query NPM page for usage instructions or review the examples for this task. The query expression can reference a helper function :get()when accessing the value of a property in which the key includes special characters. A colon ( : ) is a special character; it is used to delineate JSON keys and values. If the JSON property key includes a colon, query the property value with the get() helper function. Reference the example below (Query 9) for syntax. The value can come from a job variable, a static value, or an earlier task's outgoing variable. If the reference task is a job variable or earlier task, the reference variable shows a dropdown of available variable names for selection. If the reference task is static, the most common setting, the reference variable becomes a text edit box.
obj Object Yes Identifies the data to query. The object can come from a job variable, a static value, or an earlier task's outgoing variable. If the reference task is a job variable or earlier task, the reference variable shows a dropdown of available variable names for selection. If the reference task is static, the reference variable becomes a text edit box.


Outgoing Description
return_data The query results are assigned to immutable outgoing variable return_data. Optionally create a job variable from return_data. The immutable outgoing variable return_data remains, and a new mutable job variable is created. When creating a job variable, optionally provide a new name to avoid name conflicts and provide clarity.

Examples

This section presents several query task examples using pass_on_null and against exampleObj.

Pass on Null

When a query has no matching data and pass_on_null is true, the workflow will complete successfully. An example workflow is shown below (Figure 4).

Figure 4: Pass On Null (True)

Null is true

When a query has no matching data and pass_on_null is false, the workflow will return an error in the job history. The query will terminate in a failure. An example is shown below (Figure 5).

Figure 5: Pass On Null (False)

Null is false

Query Object

The following examples demonstrate various queries against object exampleObj.

Object 1
exampleObj = {
    "platform": {
        "ned": "cisco-ios",
        "description": "Cisco IOS Router or Switch"
    },
    "POPs": {
        "ATL": {
            "COUNTRY": "US",
            "ST": "GEORGIA",
            "LN": "ATLANTA",
            "cisco-ios-xr": [
                {
                    "name": "cr1.atl",
                    "type": "router",
                    "make": "Cisco",
                    "model": "ASR9K"
                }
            ],
            "cisco-ios": [
                {
                    "name": "er1.atl",
                    "type": "router",
                    "make": "Cisco",
                    "model": "3945"
                },
                {
                    "name": "sw1",
                    "type": "switch",
                    "make": "Cisco",
                    "model": "Catalyt 2960G"
                }
            ]
        },
        "ORF": {
            "COUNTRY": "US",
            "ST": "VIRGINIA",
            "LN": "NORFOLK",
            "cisco-ios": [
                {
                    "name": "cr1.orf",
                    "type": "router",
                    "make": "Cisco",
                    "model": "7600"
                }
            ],
            "juniper-junos": [
                {
                    "name": "er1.orf",
                    "type": "router",
                    "make": "Juniper",
                    "model": "MX10"
                }
            ]
        }
    }
}
Query 1

Extract the points of presence with a query that returns the value for key POPs.

{
    pass_on_null: true,
    query: "POPs",
    obj: exampleObj
}
Query 1 Result
{
    "ATL": {
        "COUNTRY": "US",
        "ST": "GEORGIA",
        "LN": "ATLANTA",
        "cisco-ios-xr": [
            {
                "name": "cr1.atl",
                "type": "router",
                "make": "Cisco",
                "model": "ASR9K"
            }
        ],
        "cisco-ios": [
            {
                "name": "er1.atl",
                "type": "router",
                "make": "Cisco",
                "model": "3945"
            },
            {
                "name": "sw1",
                "type": "switch",
                "make": "Cisco",
                "model": "Catalyt 2960G"
            }
        ]
    },
    "ORF": {
        "COUNTRY": "US",
        "ST": "VIRGINIA",
        "LN": "NORFOLK",
        "cisco-ios": [
            {
                "name": "cr1.orf",
                "type": "router",
                "make": "Cisco",
                "model": "7600"
            }
        ],
        "juniper-junos": [
            {
                "name": "er1.orf",
                "type": "router",
                "make": "Juniper",
                "model": "MX10"
            }
        ]
    }
}
Query 2

Refine the previous example and access an object's property. Use dot notation to access a key's value.

{
  pass_on_null: true,
  query: "POPs.ATL.cisco-ios",
  obj: exampleObj
}
Query 2 Result
[
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    },
    {
        "name": "sw1",
        "type": "switch",
        "make": "Cisco",
        "model": "Catalyt 2960G"
    }
]
Query 3

Refine the previous example. Apply a filter using syntax key=value that only matches objects where make is "Cisco".

{
  pass_on_null: true,
  query: "POPs.ATL.cisco-ios[make=Cisco]",
  obj: exampleObj
}

Notice only the first matched element is returned.

Query 3 Result
{
    "name": "er1.atl",
    "type": "router",
    "make": "Cisco",
    "model": "3945"
}
Query 4

Refine the previous example. Apply a filter to match all objects where make is "Cisco" with the asterisk operator.

{
  pass_on_null: true,
  query: "POPs.ATL.cisco-ios[*make=Cisco]",
  obj: exampleObj
}
Query 4 Result
[
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    },
    {
        "name": "sw1",
        "type": "switch",
        "make": "Cisco",
        "model": "Catalyt 2960G"
    }
]
Query 5

Refine the previous example to return all IOS routers in ATL.

{
  pass_on_null: true,
  query: "POPs.ATL.cisco-ios[*type=router]",
  obj: exampleObj
}

Since the asterisk operator's role is to match all, an array is returned even if one or no elements match.

Query 5 Result
[
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    }
]
Query 6

Perform a compound boolean query with a regular expression.

In this example, we want all IOS devices in ATL with make Cisco and a name that ends in "atl". We introduce the ampersand operator to perform a boolean AND evaluation; both conditions must be satisfied. We also introduce the tilde (~) operator to perform a regular expression match.

{
  pass_on_null: true,
  query: "POPs.ATL.cisco-ios[* make=Cisco & name~/atl$/]",
  obj: exampleObj
}
Query 6 Result
[
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    }
]
Query 7

Perform a deep query. Search through multiple levels of objects or arrays with [**]. Search for any ATL router.

{
  pass_on_null: true,
  query: "POPs.ATL[**][*type=router]",
  obj: exampleObj
}
Query 7 Result
[
    {
        "name": "cr1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "ASR9K"
    },
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    }
]
Query 8

Perform an inner query. Braces dereference the value of data in the queried object to be used for matching an object's key or an array's element.

Search for all devices in ATL of the NED value found in platform.ned.

{
  pass_on_null: true,
  query: "POPs.ATL[{platform.ned}]",
  obj: exampleObj
}
Query 8 Result
[
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    },
    {
        "name": "sw1",
        "type": "switch",
        "make": "Cisco",
        "model": "Catalyt 2960G"
    }
]
Object 2

This example demonstrates a query against exampleObj2 using the get() helper function.

Perform a query for a property who's key includes a special character. The get() helper function matches keys with special characters.

Search for any ATL:1 router in the given example object.

const exampleObj2 = {
    "platform": {
        "ned": "cisco-ios",
        "description": "Cisco IOS Router or Switch"
    },
    "POPs": {
        "ATL:1": {
            "COUNTRY": "US",
            "ST": "GEORGIA",
            "LN": "ATLANTA",
            "cisco-ios-xr": [
                {
                    "name": "cr1.atl",
                    "type": "router",
                    "make": "Cisco",
                    "model": "ASR9K"
                }
            ],
            "cisco-ios": [
                {
                    "name": "er1.atl",
                    "type": "router",
                    "make": "Cisco",
                    "model": "3945"
                },
                {
                    "name": "sw1",
                    "type": "switch",
                    "make": "Cisco",
                    "model": "Catalyt 2960G"
                }
            ]
        },
        "ORF": {
            "COUNTRY": "US",
            "ST": "VIRGINIA",
            "LN": "NORFOLK",
            "cisco-ios": [
                {
                    "name": "cr1.orf",
                    "type": "router",
                    "make": "Cisco",
                    "model": "7600"
                }
            ],
            "juniper-junos": [
                {
                    "name": "er1.orf",
                    "type": "router",
                    "make": "Juniper",
                    "model": "MX10"
                }
            ]
        }
    }
};
Query 9
{
  pass_on_null: true,
  query: "POPs.:get(ATL:1)[**][*type=router]",
  obj: exampleObj2
}
Query 9 Result
[
    {
        "name": "cr1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "ASR9K"
    },
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    }
]
Query 10
{
  pass_on_null: true,
  query: "POPs.:get(ATL:1)[**][*type=router]",
  obj: exampleObj2
}
Query 10 Result
[
    {
        "name": "cr1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "ASR9K"
    },
    {
        "name": "er1.atl",
        "type": "router",
        "make": "Cisco",
        "model": "3945"
    }
]

updateJobDescription

The updateJobDescription task allows you to update the job's description while the job is running. Job descriptions provide useful information for running, completed, and canceled jobs (example: customer name or job intent). Job descriptions are shown in Active Jobs, Job Manager, and Active Tasks applications where listed jobs can be filtered or sorted by their descriptions. When jobs start, they are passed an optional job description. This description can be edited when you manually start a job.

You can also pass a description in a REST API's POST body; however, some information isn't available when the job begins. For example, a customer may need the job description to include service instance details learned in the course of the job.

Properties

Input and output properties are shown below.

Incoming Required Description
description Yes Select the appropriate reference task then either select a reference variable or type a new description (for a static value).


Outgoing Description
description Use this immutable variable or create a job variable. Note: Other tasks cannot directly access job descriptions, but the new description can be read from updateJobDescription's outgoing variable.